package com.aiwiown.snackmq.common.message;

import com.aiwiown.snackmq.common.compress.Compressor;
import com.aiwiown.snackmq.common.compress.CompressorFactory;
import com.aiwiown.snackmq.common.constant.MessageConstants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Singular;
import lombok.ToString;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.CRC32;

/**
 * SnackMQ 中消息的核心模型。
 * 这是一个功能丰富的领域对象，不仅包含数据，还封装了相关的业务逻辑。
 */
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Message implements Serializable {

    private static final long serialVersionUID = 1929566362823492887L;
    /**
     * 【新增】用于在消息属性中存储其在全局 CommitLog 中的物理偏移量（Raft 日志索引）的键。
     * 这是一个内部属性，主要用于延迟消息的恢复。
     */
    public static final String PROPERTY_PHYSICAL_OFFSET = "__physicalOffset";
    // --- 核心消息标识 ---
    private long messageId;
    private MessageType type;
    private String topic;
    /**
     * 消息的业务主键或路由键 (Routing Key)。
     * <p>
     * 1. <b>在发送到 Exchange 时</b>: 此字段作为消息的路由键，用于 Exchange 判断消息应被投递到哪个队列。
     *    例如，在 TopicExchange 中，此 key 会与绑定模式进行通配符匹配。
     * 2. <b>在直接发送到 Topic (队列) 时</b>: 此字段可用于分区策略计算，确保同一 key 的消息落到同一个分区，以保证顺序性。
     * 3. <b>对于控制类消息 (如 BIND_QUEUE_REQUEST)</b>: 此字段不应被使用。相关参数应通过 {@link #properties} 传递。
     */
    private String key;
    private String tag;

    // --- 消息内容 ---
    private byte[] body;

    // --- 新增：批量消息支持 ---
    private List<Message> messages;

    // --- 元数据与属性 ---
    @Builder.Default
    private int version = MessageConstants.CURRENT_VERSION;

    @Singular
    private Map<String, String> properties;

    private String compressionAlgorithm;
    private long checksum;

    // --- 生命周期与路由 ---
    @Builder.Default
    private long createTime = System.currentTimeMillis();
    private long expireTime;
    @Builder.Default
    private int priority = 0;
    @Builder.Default
    private int retryCount = 0;
    private String source;
    private String target;

    // --- 状态 ---
    private MessageStatus status;
    private String errorMessage;

    // --- 业务逻辑方法 ---

    /**
     * 检查此消息是否为一个批次。
     *
     * @return 如果 messages 列表不为空，则返回 true；否则返回 false。
     */
    public boolean isBatch() {
        return this.messages != null && !this.messages.isEmpty();
    }

    /**
     * 向消息的属性集合中添加一个键值对。
     *
     * @param key   属性的键。
     * @param value 属性的值。
     */
    public void addProperty(String key, String value) {
        if (this.properties == null) {
            this.properties = new HashMap<>();
        }
        this.properties.put(key, value);
    }

    /**
     * 获取指定键的属性值。
     *
     * @param key 属性的键。
     * @return 如果属性集合中存在该键，则返回对应的值；否则返回 null。
     */
    public String getProperty(String key) {
        if (this.properties == null) {
            return null;
        }
        return this.properties.get(key);
    }

    /**
     * 获取指定键的属性值，并提供默认值。
     *
     * @param key          属性的键。
     * @param defaultValue 如果属性集合中不存在该键，则返回的默认值。
     * @return 如果属性集合中存在该键，则返回对应的值；否则返回默认值。
     */
    public String getProperty(String key, String defaultValue) {
        if (this.properties == null) {
            return defaultValue;
        }
        return this.properties.getOrDefault(key, defaultValue);
    }

    /**
     * 检查消息是否已过期。
     *
     * @return 如果消息设置了过期时间且当前时间超过过期时间，则返回 true；否则返回 false。
     */
    public boolean isExpired() {
        return this.expireTime > 0 && System.currentTimeMillis() > this.expireTime;
    }

    /**
     * 检查消息是否可以重试。
     *
     * @return 如果当前重试次数小于最大重试次数，则返回 true；否则返回 false。
     */
    public boolean canRetry() {
        return this.retryCount < MessageConstants.MAX_RETRY_COUNT;
    }

    /**
     * 增加重试次数。
     */
    public void incrementRetryCount() {
        this.retryCount++;
    }

    /**
     * 检查消息体是否已被压缩。
     *
     * @return 如果压缩算法字段不为空且非空字符串，则返回 true；否则返回 false。
     */
    public boolean isCompressed() {
        return this.compressionAlgorithm != null && !this.compressionAlgorithm.isEmpty();
    }

    /**
     * 压缩消息体。
     *
     * @param algorithm 压缩算法名称。
     */
    public void compress(String algorithm) {
        if (this.body == null || this.body.length == 0 || isCompressed()) {
            return;
        }
        Compressor compressor = CompressorFactory.getCompressor(algorithm);
        this.body = compressor.compress(this.body);
        this.compressionAlgorithm = algorithm;
    }

    /**
     * 解压缩消息体。
     */
    public void decompress() {
        if (this.body == null || this.body.length == 0 || !isCompressed()) {
            return;
        }
        Compressor compressor = CompressorFactory.getCompressor(this.compressionAlgorithm);
        this.body = compressor.decompress(this.body);
        this.compressionAlgorithm = null;
    }

    /**
     * 计算消息体的校验和。
     */
    public void calculateChecksum() {
        if (this.body == null) {
            this.checksum = 0;
            return;
        }
        CRC32 crc32 = new CRC32();
        crc32.update(this.body);
        this.checksum = crc32.getValue();
    }

    /**
     * 验证消息体的校验和是否正确。
     *
     * @return 如果消息体为空且校验和为 0，或者消息体的校验和与存储的校验和一致，则返回 true；否则返回 false。
     */
    public boolean validateChecksum() {
        if (this.body == null) {
            return this.checksum == 0;
        }
        CRC32 crc32 = new CRC32();
        crc32.update(this.body);
        return crc32.getValue() == this.checksum;
    }

    /**
     * 【新增】获取消息的延迟时间（秒）。
     * @return 延迟秒数，0 表示不延迟。
     */
    public long getDelayTimeInSeconds() {
        String delayStr = getProperty("delayTimeInSeconds");
        if (delayStr == null) {
            return 0;
        }
        try {
            return Long.parseLong(delayStr);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}
